/*
 * Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.huawei.cloudphone.service;

import android.os.RemoteException;
import android.view.Surface;

import com.huawei.cloudphone.apiimpl.CloudPhoneImpl;
import com.huawei.cloudphone.audio.AudioTrackerCallback;
import com.huawei.cloudphone.common.CASLog;
import com.huawei.cloudphone.datacenter.CasRecvPktDispatcher;
import com.huawei.cloudphone.datacenter.NewPacketCallback;
import com.huawei.cloudphone.jniwrapper.JNIWrapper;
import com.huawei.cloudphone.jniwrapper.JniBridge;
import com.huawei.cloudphone.common.CasConnectorInfo;
import com.huawei.cloudphone.common.CasParcelableMap;
import com.huawei.cloudphone.utils.CasAESUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_AES_IV;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_AUTH_TS;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_CLIENT_MODE;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_ENCRYPTED_DATA;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_BACKGROUND_TIMEOUT;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_IP;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_PORT;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_PROTOCOL_VERSION;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_REGION_ID;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_SDK_VERSION;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_SESSION_ID;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_TICKET;
import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_VERIFY_DATA;

/**
 * CasProcessor
 */
public class CasProcessor {

    /**
     * TAG:CasProcessor
     */
    private static final String TAG = "CasProcessor";

    /**
     * upstream receive dispatcher
     */
    private CasRecvPktDispatcher mUpstreamReceiveDispatcher = null;

    /**
     * audio tracker callback
     */
    private AudioTrackerCallback mAudioTrackerCallback = null;

    /**
     * channel data callback
     */
    private NewChannelDataPacket mChannelDataCallback = null;

    /**
     * surface
     */
    private Surface mSurface = null;

    /**
     * listener
     */
    private CloudPhoneImpl.CASListener mListener = null;
    private NewRotationDirectionPacket mNewRotationDirPkt;
    private NewVirtualDevDataPacket mVirtualDevDataPkt;
    private NewImeDataPacket mImeDataPkt;

    public void init() {
        CASLog.i(TAG, "init....");
        CasInteractiveStateCallback interactiveStateCallback = new CasInteractiveStateCallback() {
            @Override
            public void onCmdReceive(int code, String msg) {
                if (mListener != null) {
                    try {
                        mListener.onCmdRecv(code, msg);
                    } catch (RemoteException e) {
                        CASLog.e(TAG, "onCmdReceive::failed to notify cas state.");
                    }
                }
            }

            @Override
            public void onBitrateCallback(int bitrate) {
                CASLog.i(TAG, "Recieve bitrate call back from jni, m_trans send bitrate is: " + bitrate);
                if (mListener != null) {
                    try {
                        mListener.onBitrateRecv(bitrate);
                    } catch (RemoteException e) {
                        CASLog.e(TAG, "onBitrateCallback::failed to notify cas state.");
                    }
                }
            }
        };
        JniBridge.getInstance().registerCasJNICallback(interactiveStateCallback);
    }

    public void setEncryptData(String encryptData) {
        CASLog.i(TAG, "setEncryptData....");
        JniBridge.getInstance().setJniConf(KEY_ENCRYPTED_DATA, encryptData);
    }

    public void setSurface(Surface suf) {
        CASLog.i(TAG, "setSurface....");
        mSurface = suf;
    }

    public boolean start(boolean isHome) {
        CASLog.i(TAG, "start....");
        return JniBridge.getInstance().start(mSurface, isHome);
    }

    public void stop(final boolean isHome) {
        CASLog.i(TAG, "stop....");
        JniBridge.getInstance().stop(isHome);
    }

    public void pause() {
        CASLog.i(TAG, "pause....");
        stopJniRecv();
    }

    public void resume() {
        CASLog.i(TAG, "resume....");
    }

    public boolean isConnect() {
        boolean isConnect = JNIWrapper.getConnectStatus();
        return isConnect;
    }

    public int getState() {
        CASLog.i(TAG, "getState....");
        return JNIWrapper.getJniStatus();
    }

    public boolean reconnect() {
        boolean ret = JNIWrapper.reconnect();
        CASLog.i(TAG, "reconnect ret = " + ret);
        return ret;
    }

    public void registerListener(CloudPhoneImpl.CASListener listener) {
        CASLog.i(TAG, "registerListener....");
        mListener = listener;
    }

    public void unregisterListener() {
        CASLog.i(TAG, "unregisterListener....");
        mListener = null;
    }

    public boolean sendTouchEvent(int id, int action, int x, int y, int pressure, long time, int orientation, int height, int width) {
        return JniBridge.getInstance().sendTouchEvent(id, action, x, y, pressure, time, orientation, height, width);
    }

    public boolean sendKeyEvent(int keycode, int action) {
        CASLog.i(TAG, "sendKeyEvent, keycode:" + keycode + "action:" + action);
        return JniBridge.getInstance().sendKeyEvent(keycode, action);
    }

    public boolean sendMotionEvent(int materAxis, int materValue, int secondaryAxis, int secondaryValue) {
        CASLog.i(TAG, "sendMotionEvent, materAxis:" + materAxis + "materValue:" + materValue + "secondaryAxis:" + secondaryAxis + "secondaryValue:" + secondaryValue);
        return JniBridge.getInstance().sendMotionEvent(materAxis, materValue, secondaryAxis, secondaryValue);
    }

    public void setMediaConfig(final CasParcelableMap mediaConfigMap) {
        HashMap<String, String> mediaConfig = mediaConfigMap.getParcelableMap();
        JniBridge.getInstance().setMediaConfig(mediaConfig);
    }

    public boolean setRotation(int rotation) {
        CASLog.i(TAG, "setRotation... ");
        return JniBridge.getInstance().setRotation(rotation);
    }

    public int getLag() {
        return JniBridge.getInstance().getLag();
    }

    public String getVideoStreamStats() {
        return JniBridge.getInstance().getVideoStreamStats();
    }

    public String getSimpleStreamStats() {
        return JniBridge.getInstance().getSimpleStreamStats();
    }

    public boolean startJniRecv() {
        CASLog.i(TAG, "startJniRecv... ");
        mUpstreamReceiveDispatcher = new CasRecvPktDispatcher();
        mAudioTrackerCallback = new AudioTrackerCallback();
        mNewRotationDirPkt = new NewRotationDirectionPacket();
        mChannelDataCallback = new NewChannelDataPacket();
        mVirtualDevDataPkt = new NewVirtualDevDataPacket();
        mImeDataPkt = new NewImeDataPacket();

        mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.ORIENTATION, mNewRotationDirPkt);
        mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.AUDIO, mAudioTrackerCallback);
        mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.CHANNEL, mChannelDataCallback);
        mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.VIRTUAL_DEVICE_DATA, mVirtualDevDataPkt);
        mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.IMEDATA, mImeDataPkt);
        mUpstreamReceiveDispatcher.start();
        return true;
    }

    public void stopJniRecv() {
        CASLog.i(TAG, "stopJniRecv... ");
        if (mUpstreamReceiveDispatcher != null) {
            mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.AUDIO);
            mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.ORIENTATION);
            mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.CHANNEL);
            mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.VIRTUAL_DEVICE_DATA);
            mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.IMEDATA);
            mUpstreamReceiveDispatcher.stopBlocked();
            mUpstreamReceiveDispatcher = null;
            mAudioTrackerCallback.closeAudioTrack();
            mAudioTrackerCallback = null;
        }
    }

    public void setCasConnectorInfo(CasConnectorInfo info) {
        CASLog.i(TAG, "setCasConnectorInfo...");

        JniBridge.getInstance().setJniConf(KEY_IP, info.getConnectIp());
        JniBridge.getInstance().setJniConf(KEY_PORT, info.getConnectPort());
        JniBridge.getInstance().setJniConf(KEY_TICKET, info.getTicket());
        JniBridge.getInstance().setJniConf(KEY_SESSION_ID, info.getSessionId());
        JniBridge.getInstance().setJniConf(KEY_AES_IV, info.getAesIv());
        JniBridge.getInstance().setJniConf(KEY_AUTH_TS, info.getAuthTs());
        JniBridge.getInstance().setJniConf(KEY_SDK_VERSION, info.getSdkVersion());
        JniBridge.getInstance().setJniConf(KEY_BACKGROUND_TIMEOUT, info.getBackgroundTimeout());
        JniBridge.getInstance().setJniConf(KEY_PROTOCOL_VERSION, info.getProtocolVersion());
        JniBridge.getInstance().setJniConf(KEY_VERIFY_DATA, buildVerifyDataInfo(info));
        JniBridge.getInstance().setJniConf(KEY_REGION_ID, info.getRegionId());
        JniBridge.getInstance().setJniConf(KEY_CLIENT_MODE, info.getClientMode());

        String encryptedData = "";
        try {
            encryptedData = buildEncryptedDataInfo(info);
        } catch (JSONException e) {
            CASLog.e(TAG, "failed to build encrypted data info.");
        }
        JniBridge.getInstance().setJniConf(KEY_ENCRYPTED_DATA, encryptedData);
    }

    public void sendData(byte devType, byte[] data) {
        JniBridge.getInstance().sendData(devType, data, data.length);
    }

    /**
     * build verify data info
     */
    private String buildVerifyDataInfo(CasConnectorInfo connectorInfo) {
        String verifyStr = connectorInfo.getConnectIp()
                + connectorInfo.getConnectPort()
                + connectorInfo.getSessionId()
                + connectorInfo.getBackgroundTimeout()
                + connectorInfo.getAvailablePlayTime()
                + connectorInfo.getUserId();
        String verifyData = "";
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("sha-256");
            messageDigest.update(verifyStr.getBytes("utf-8"));
            verifyData = CasAESUtils.bytesToHex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            CASLog.e(TAG, "failed to build verify data info.");
        } catch (UnsupportedEncodingException e) {
            CASLog.e(TAG, "failed to build verify data info.");
        }

        return verifyData;
    }

    /**
     * build encrypted data info
     */
    private String buildEncryptedDataInfo(CasConnectorInfo connectorInfo) throws JSONException {
        JSONObject jsonObj = new JSONObject();
        jsonObj.put("ip", connectorInfo.getConnectIp());
        jsonObj.put("port", connectorInfo.getConnectPort());
        jsonObj.put("session_id", connectorInfo.getSessionId());
        jsonObj.put("backgroundTimeout", connectorInfo.getBackgroundTimeout());
        jsonObj.put("available_playtime", connectorInfo.getAvailablePlayTime());
        jsonObj.put("user_id", connectorInfo.getUserId());
        jsonObj.put("touch_timeout", connectorInfo.getTouchTimeout());
        // 加密信息
        String encryptedData = CasAESUtils.encryptWithAESGCM(jsonObj.toString(), connectorInfo.getAesKey(), connectorInfo.getAesIv());
        return encryptedData;
    }

    private class NewRotationDirectionPacket implements NewPacketCallback {
        @Override
        public void onNewPacket(byte[] data) {
            int rotation = data[0] & 0xFF;
            CASLog.i(TAG, "rotation change " + rotation);
            if (null != mListener) {
                try {
                    mListener.onRotationDirectionChange(rotation);
                } catch (RemoteException e) {
                    CASLog.e(TAG, "call onRotationDirectionChange failed.");
                }
            }
        }
    }

    private class NewChannelDataPacket implements NewPacketCallback {
        @Override
        public void onNewPacket(byte[] data) {
            if (mListener != null) {
                try {
                    mListener.onChannelDataRecv(data);
                } catch (RemoteException e) {
                    CASLog.e(TAG, "call onChannelDataRecv failed." + e.getMessage());
                }
            }
        }
    }

    private class NewVirtualDevDataPacket implements NewPacketCallback {
        @Override
        public void onNewPacket(byte[] data) {
            if (mListener != null) {
                try {
                    mListener.onVirtualDevDataRecv(data);
                } catch (RemoteException e) {
                    CASLog.e(TAG, "call onVirtualDevDataRecv failed." + e.getMessage());
                }
            }
        }
    }

    private class NewImeDataPacket implements NewPacketCallback {
        @Override
        public void onNewPacket(byte[] data) {
            if (mListener != null) {
                try {
                    mListener.onImeMsgRecv(data);
                } catch (RemoteException e) {
                    CASLog.e(TAG, "call onImeMsgRecv failed.");
                }
            }
        }
    }
}
